Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 192ac21fb0329bdc290881cc2e8c307030e4c9e1


Parents : f717d50
Author : Sudo-Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-01-07T19:46:01-06:00

feat(docs): add API endpoints for deleting documentation versions and clearing Reticulum docs

- Implemented DELETE endpoints to allow users to delete specific documentation versions and clear all Reticulum documentation.
- Enhanced the DocsManager class with methods for version deletion and clearing documentation, including error handling and logging.
- Updated frontend components to support version deletion and clearing of Reticulum docs with user confirmation dialogs.

Changes
Diff

diff --git a/meshchatx/meshchat.py b/meshchatx/meshchat.py
index aa04c0bb..bad06207 100644
--- a/meshchatx/meshchat.py
+++ b/meshchatx/meshchat.py
@@ -3753,6 +3753,31 @@ class ReticulumMeshChat:
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
+ # delete docs version
+ @routes.delete("/api/v1/docs/version/{version}")
+ async def docs_delete_version(request):
+ try:
+ version = request.match_info.get("version")
+ if not version:
+ return web.json_response(
+ {"error": "No version provided"},
+ status=400,
+ )
+
+ success = self.docs_manager.delete_version(version)
+ return web.json_response({"success": success})
+ except Exception as e:
+ return web.json_response({"error": str(e)}, status=500)
+
+ # clear reticulum docs
+ @routes.delete("/api/v1/maintenance/docs/reticulum")
+ async def docs_clear(request):
+ try:
+ success = self.docs_manager.clear_reticulum_docs()
+ return web.json_response({"success": success})
+ except Exception as e:
+ return web.json_response({"error": str(e)}, status=500)
+
# search docs
@routes.get("/api/v1/docs/search")
async def docs_search(request):
@@ -8901,7 +8926,7 @@ class ReticulumMeshChat:
if "telemetry_enabled" in data:
self.config.telemetry_enabled.set(
- self._parse_bool(data["telemetry_enabled"])
+ self._parse_bool(data["telemetry_enabled"]),
)
# update banishment settings
@@ -10285,7 +10310,7 @@ class ReticulumMeshChat:
commands.extend(val)
elif isinstance(val, dict):
commands.append(val)
- if 0x01 in lxmf_fields and 0x01 != LXMF.FIELD_COMMANDS:
+ if 0x01 in lxmf_fields and LXMF.FIELD_COMMANDS != 0x01:
val = lxmf_fields[0x01]
if isinstance(val, list):
commands.extend(val)
@@ -10321,11 +10346,11 @@ class ReticulumMeshChat:
else:
# Check if peer is trusted
contact = ctx.database.contacts.get_contact_by_identity_hash(
- source_hash
+ source_hash,
)
if not contact or not contact.get("is_telemetry_trusted"):
print(
- f"Telemetry request from untrusted peer {source_hash}, ignoring"
+ f"Telemetry request from untrusted peer {source_hash}, ignoring",
)
else:
print(f"Responding to telemetry request from {source_hash}")
@@ -10342,7 +10367,7 @@ class ReticulumMeshChat:
"remote_identity_name": source_hash[:8],
"lxmf_message": convert_db_lxmf_message_to_dict(
ctx.database.messages.get_lxmf_message_by_hash(
- lxmf_message.hash.hex()
+ lxmf_message.hash.hex(),
),
include_attachments=False,
),

diff --git a/meshchatx/src/backend/docs_manager.py b/meshchatx/src/backend/docs_manager.py
index d78d0317..bc41803b 100644
--- a/meshchatx/src/backend/docs_manager.py
+++ b/meshchatx/src/backend/docs_manager.py
@@ -134,6 +134,63 @@ class DocsManager:
return True
return False
+ def delete_version(self, version):
+ """Deletes a specific version of documentation."""
+ if version not in self.get_available_versions():
+ return False
+
+ version_path = os.path.join(self.versions_dir, version)
+ if not os.path.exists(version_path):
+ return False
+
+ try:
+ # If the deleted version is the current one, unlink 'current' first
+ current_version = self.get_current_version()
+ if current_version == version:
+ if os.path.exists(self.docs_dir):
+ if os.path.islink(self.docs_dir):
+ os.unlink(self.docs_dir)
+ else:
+ shutil.rmtree(self.docs_dir)
+
+ shutil.rmtree(version_path)
+
+ # If we just deleted the current version, try to pick another one as current
+ if current_version == version:
+ self._update_current_link()
+
+ return True
+ except Exception as e:
+ logging.exception(f"Failed to delete docs version {version}: {e}")
+ return False
+
+ def clear_reticulum_docs(self):
+ """Clears all Reticulum documentation and versions."""
+ try:
+ if os.path.exists(self.docs_base_dir):
+ # We don't want to delete the base dir itself, just its contents
+ # except possibly some metadata if we added any.
+ # Actually, deleting everything inside reticulum-docs is fine.
+ for item in os.listdir(self.docs_base_dir):
+ item_path = os.path.join(self.docs_base_dir, item)
+ if os.path.islink(item_path):
+ os.unlink(item_path)
+ elif os.path.isdir(item_path):
+ shutil.rmtree(item_path)
+ else:
+ os.remove(item_path)
+
+ # Re-create required subdirectories
+ for d in [self.versions_dir, self.docs_dir]:
+ if not os.path.exists(d):
+ os.makedirs(d)
+
+ self.config.docs_downloaded.set(False)
+ return True
+ except Exception as e:
+ logging.exception(f"Failed to clear Reticulum docs: {e}")
+ return False
+
def populate_meshchatx_docs(self):
"""Populates meshchatx-docs from the project's docs folder."""
# Try to find docs folder in several places

diff --git a/meshchatx/src/frontend/components/docs/DocsPage.vue b/meshchatx/src/frontend/components/docs/DocsPage.vue
index ae9b1d83..1d438243 100644
--- a/meshchatx/src/frontend/components/docs/DocsPage.vue
+++ b/meshchatx/src/frontend/components/docs/DocsPage.vue
@@ -114,7 +114,7 @@
<button
v-for="version in status.versions"
:key="version"
- class="w-full px-4 py-2 text-left text-[11px] hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors flex items-center justify-between"
+ class="w-full px-4 py-2 text-left text-[11px] hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors flex items-center justify-between group"
:class="
status.current_version === version
? 'text-blue-600 dark:text-blue-400 font-bold'
@@ -122,12 +122,23 @@
"
@click="switchVersion(version)"
>
- <span>{{ version }}</span>
- <MaterialDesignIcon
- v-if="status.current_version === version"
- icon-name="check"
- class="w-3.5 h-3.5"
- />
+ <span class="truncate">{{ version }}</span>
+ <div class="flex items-center space-x-1">
+ <MaterialDesignIcon
+ v-if="status.current_version === version"
+ icon-name="check"
+ class="w-3.5 h-3.5"
+ />
+ <button
+ v-if="status.versions.length > 1"
+ type="button"
+ class="p-1 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
+ title="Delete this version"
+ @click.stop="deleteVersion(version)"
+ >
+ <MaterialDesignIcon icon-name="delete" class="w-3.5 h-3.5" />
+ </button>
+ </div>
</button>
<div
v-if="status.versions.length === 0"
@@ -715,6 +726,20 @@ export default {
console.error("Failed to switch docs version:", error);
}
},
+ async deleteVersion(version) {
+ if (!confirm(`Are you sure you want to delete version "${version}"?`)) {
+ return;
+ }
+
+ try {
+ await window.axios.delete(`/api/v1/docs/version/${encodeURIComponent(version)}`);
+ this.fetchStatus();
+ ToastUtils.success(`Version ${version} deleted`);
+ } catch (error) {
+ console.error("Failed to delete docs version:", error);
+ ToastUtils.error("Failed to delete version: " + (error.response?.data?.error || error.message));
+ }
+ },
async handleZipUpload(event) {
const file = event.target.files[0];
if (!file) return;

diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue
index a95b4d7a..894d4d59 100644
--- a/meshchatx/src/frontend/components/settings/SettingsPage.vue
+++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue
@@ -303,6 +303,22 @@
</div>
</div>
</button>
+
+ <button
+ type="button"
+ class="btn-maintenance border-orange-200 dark:border-orange-900/30 text-orange-700 dark:text-orange-300 bg-orange-50 dark:bg-orange-900/10 hover:bg-orange-100 dark:hover:bg-orange-900/20"
+ @click="clearReticulumDocs"
+ >
+ <div class="flex flex-col items-start text-left">
+ <div class="font-bold flex items-center gap-2">
+ <MaterialDesignIcon icon-name="book-remove" class="size-4" />
+ {{ $t("maintenance.clear_reticulum_docs") }}
+ </div>
+ <div class="text-xs opacity-80">
+ {{ $t("maintenance.clear_reticulum_docs_desc") }}
+ </div>
+ </div>
+ </button>
</div>
<div class="space-y-2 pt-2 border-t border-gray-100 dark:border-zinc-800">
@@ -2218,6 +2234,15 @@ export default {
ToastUtils.error(this.$t("common.error"));
}
},
+ async clearReticulumDocs() {
+ if (!(await DialogUtils.confirm(this.$t("maintenance.clear_confirm")))) return;
+ try {
+ await window.axios.delete("/api/v1/maintenance/docs/reticulum");
+ ToastUtils.success(this.$t("maintenance.docs_cleared"));
+ } catch {
+ ToastUtils.error(this.$t("common.error"));
+ }
+ },
async exportMessages() {
try {
const response = await window.axios.get("/api/v1/maintenance/messages/export");


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────